package sk.lieskove301.jianghongtiao.liaad.prisoner.agent;

import sk.lieskove301.jianghongtiao.liaad.prisoner.gene.GeneFunction;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import sk.lieskove301.jianghongtiao.liaad.prisoner.gene.GeneticInfo;
import sk.lieskove301.jianghongtiao.liaad.prisoner.memory.ForgettingEnum;
import sk.lieskove301.jianghongtiao.liaad.prisoner.payoff.PayoffValue;
import sk.lieskove301.jianghongtiao.liaad.prisoner.strategy.FightResultEnum;

/**
 * Abstract prisoner. This prisoner implements common methods without any strategy
 * @author xjuraj
 */
public class AbstractAgent implements Agent, Comparable<Agent>{

    private GeneticInfo gene;
    private Map<Agent, Deque<FightResultEnum>> memories =
            new HashMap<Agent, Deque<FightResultEnum>>();
    private ForgettingEnum forgetting;
    private double score = 0;
    private PayoffValue payoff;
    private int bornYear;
    private boolean optimistic = false;
    private boolean inverted = false;

    public boolean invDecision(boolean normalDecision){
        if(inverted){
            return !normalDecision;
        }
        return normalDecision;
    }
    
    /**
     * count cooperation of agent based on previous 
     * @param p agent with which we are fighting
     * @param round
     * @return
     */
    public boolean cooperate(Agent p, int round) {
        Deque memory = memories.get(p);
        boolean result;
        if((memory == null) || (memory.size() == 0)){
            result = invDecision(GeneFunction.cooperateByGene(gene.firstMove()));
        } else {
            if(optimistic){
                result = GeneFunction.cooperateByGene(gene.get(
                    forgetting.value().countCooperateMemories(memory).value()));
            } else {
                result = GeneFunction.cooperateByGene(gene.get(
                    forgetting.value().countBetrayMemories(memory).value()));
            }
            result = invDecision(result);
        }
        return result;
    }

    /**
     * get gene of agent
     * @return agent's gene
     */
    public GeneticInfo getGene() {
        return gene;
    }

    /**
     * set agent's gene
     * @param gene new gene of agent
     */
    public void setGene(GeneticInfo gene) {
        this.gene = gene;
    }

    /**
     * set fight results of two agents
     * @param fightWith agent with which was fight
     * @param fightResult results of this fight
     */
    public void setFightResults(Agent fightWith, FightResultEnum fightResult) {
        if(fightWith == null) throw new NullPointerException("fight agent is null");
//        if(fightResult == null) throw new NullPointerException("fight results are null");
//        if(fightResult.size() != 2) throw new IllegalArgumentException("fight results have bad size.");
        Deque memory = null;
        //if agent is in and had already fight
        if(memories.containsKey(fightWith)){
            memory = memories.get(fightWith);
            memory.addFirst(fightResult);
        } else {
            //for new agent without memories
            memory = new ArrayDeque();
            memory.addFirst(fightResult);
        }
        //replace/create memories for agent
        memories.put(fightWith, memory);
        //add value from fight ressults
        score += payoff.countPayoffValues(fightResult).getFirst();
    }

    /**
     * remove killed agents from agent's memory
     * @param deadSouls
     */
    public void buryAgents(Collection deadSouls) {
        for (Iterator it = deadSouls.iterator(); it.hasNext();) {
            Agent a = (Agent) it.next();
            memories.remove(a);
            if(memories.containsKey(a)){
                throw new IllegalStateException("Object still resists in memory");
            }
        }
    }

    /**
     * get score of agent
     * @return agent's actual score
     */
    public double getScore() {
        return score;
    }

    public int getBirthday() {
        return bornYear;
    }

    public void setBirthday(int bornYear) {
        this.bornYear = bornYear;
    }

    public void amnesia() {
        memories = new HashMap<Agent, Deque<FightResultEnum>>();
    }

    public int getBornYear() {
        return bornYear;
    }

    public void setBornYear(int bornYear) {
        this.bornYear = bornYear;
    }

    public ForgettingEnum getForgetting() {
        return forgetting;
    }

    public void setForgetting(ForgettingEnum forgetting) {
        this.forgetting = forgetting;
    }

    public PayoffValue getPayoff() {
        return payoff;
    }

    public void setPayoff(PayoffValue payoff) {
        this.payoff = payoff;
    }

    public int compareTo(Agent o) {
        return Double.compare(score, o.getScore());
    }

    public Agent copyAgent() {
        AbstractAgent a = new AbstractAgent();
        a.setGene(gene.copyGene());
        a.setPayoff(payoff);
        a.setForgetting(forgetting);
        a.setInvert(inverted);
        a.setOptimistic(optimistic);
        if(this.gene == a.getGene()){
            throw new IllegalStateException("Gene was not copied!");
        }
        //a.score = score;
        return a;
    }

    public Deque<FightResultEnum> getMemory(Agent a) {
        return memories.get(a);
    }

    public void totalAmnesia() {
        amnesia();
        score=0;
    }

    public void setInvert(boolean inverted) {
        this.inverted = inverted;
    }

    public boolean getInvert() {
        return inverted;
    }

    public void setOptimistic(boolean optimistic) {
        this.optimistic = optimistic;
    }

    public boolean getOptimistic() {
        return optimistic;
    }

    @Override
    public String toString() {
        String result = gene.toString()+forgetting.value().getLetter();
        if(optimistic){
            result += "O";
        } else {
            result += "P";
        }
        if(inverted){
            result += "I";
        } else {
            result += "N";
        }
        return result;
    }

}
